/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <arpa/telnet.h>

#include "Connection.h"
#include "Logger.h"
#include "Error.h"

using namespace Chronos;

namespace Chronos {

class SocketConnection : public Connection {
public:
    SocketConnection(int fd_, in_addr ipaddr_)
		: fd(fd_)
		, ipaddr(ipaddr_)
	{}
    virtual ~SocketConnection() {
    	close(fd);
	}

    virtual int read (char* buf, unsigned int size) {
		int result = ::read(fd, buf, size);
		if (result > 0) return result;
		if (result == -1 && errno == EAGAIN) {
			return Connection::NODATA;
		}
		return Connection::ERROR;
	}
    virtual void write(const char* text) {
    	::send(fd, text, strlen(text), MSG_NOSIGNAL);
    }
    virtual void write(const char* text, int size) {
    	::send(fd, text, size, MSG_NOSIGNAL);
	}
    virtual void setEcho(bool echo) {
	    char echo_off[]={IAC,WILL,TELOPT_ECHO,'\0'};
	    char echo_on[]={IAC,WONT,TELOPT_ECHO,'\n','\r','\0'};
		
		char* buf = (echo) ? &echo_on[0] : &echo_off[0];
	    ::send(fd, buf, strlen(buf), MSG_NOSIGNAL);
	}
private:
    int fd;
    in_addr ipaddr;

    SocketConnection(const SocketConnection&);
    SocketConnection& operator=(const SocketConnection&);
};

}


SocketServer::SocketServer(unsigned int port_)
	: port(port_)
{
    sin.sin_family = AF_INET;
    sin.sin_port = htons (port_);
    sin.sin_addr.s_addr = INADDR_ANY;
    memset(&(sin.sin_zero), '\0', 8);

    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd  == -1) {
        throw ServerError("error opening socket");
    }

    int reuse = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) == -1) {
        throw ServerError("error setting socket options");
    }

    if (bind(fd, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) == -1) {
        close(fd);
        throw ServerError("socket already in use");
    }
    if (listen (fd, MAX_PENDING_CONNECTIONS) == -1) {
        close(fd);
        throw ServerError("error listening on socket");
    }
    LOG(INFO, "SocketServer: listening on port %d", port);
}


SocketServer::~SocketServer() {
    close(fd);
    LOG(INFO, "SocketServer: closed port %d", port);
}


bool SocketServer::hasNewConnection() const {
    fd_set input_set, exception_set;
    struct timeval null_time;

    null_time.tv_usec = 0;
    null_time.tv_sec = 0;

    FD_ZERO(&input_set);
    FD_ZERO(&exception_set);
    FD_SET(fd, &input_set);

    if (select(fd+1, &input_set, NULL, NULL, &null_time) == -1) {
        LOG(DEBUG, "SocketServer::%s() error with select", __func__);
        return false;
    }

    if (FD_ISSET(fd, &exception_set) ) {
        LOG(DEBUG, "SocketServer::%s() Exception pending!", __func__);
    }
    if (FD_ISSET(fd, &input_set) ) { //there is a new connection or new input
        return true;
    }
    return false;
}


Connection* SocketServer::acceptConnection() const {
    struct sockaddr_in remote;
    unsigned int sin_size = sizeof(struct sockaddr_in);
    int newfd = accept(fd,(struct sockaddr *) &remote, &sin_size);
    if (newfd == -1) {
        throw ConnectionError("error accepting new connection");
    }

    LOG(DEBUG, "SocketServer: received connection %d from %s", newfd, inet_ntoa(remote.sin_addr));
    if (fcntl(newfd, F_SETFL, O_NONBLOCK) == -1) {
        close(newfd);
        throw ConnectionError("error setting option O_NONBLOCK");
    }
    return new SocketConnection(newfd, remote.sin_addr);
}


